from odoo import fields, models, api
from odoo.exceptions import Warning
import logging

_logger = logging.getLogger(__name__)

class Book(models.Model):
    """

    """
    _name = 'library.book'
    _description = 'Book'
    # 排序字段：以图书名排序，然后按出版日期倒序排（新出版在前）
    _order = 'name, date_published desc'

    # 每个字段都带有所需的位置参数
    # String fields
    name = fields.Char('Title', required=True)
    isbn = fields.Char('ISBN')
    # 选项位置参数是一个[(‘value’, ‘Title’),]元组列表
    # 元组第一个元素是存储在数据库中的值，第二个元素是展示在用户界面中的描述
    book_type = fields.Selection(
        [('paper', 'Paperback'),
         ('hard', 'Hardcover'),
         ('electronic', 'Electronic'),
         ('other', 'Other')],
        'Type')
    notes = fields.Text('Internal Notes')
    descr = fields.Html('Description')

    # Numeric fields:
    # Integer(string)仅需字段标题字符串参数
    copies = fields.Integer(default=1)
    # Float(string, digits)带有第二个可选参数digits，该字段是一个指定字段精度的(x,y)元组，x 是数字总长，y 是小数位
    avg_rating = fields.Float('Average Rating', (3, 2))
    # Monetary(string, currency_field)与浮点字段类似，但带有货币的特殊处理。第二个参数currency_field用于存储所使用货币，默认应传入currency_id字段
    price = fields.Monetary('Price', 'currency_id')
    currency_id = fields.Many2one('res.currency')  # price helper

    # Date and time fields
    # Date(string)和Datetime(string)字段只需一个字符串文本位置参数
    date_published = fields.Date()
    # 对于简单运算，可使用 lambda 函数来避免过重的有名函数或方法的创建qqqq
    last_borrow_date = fields.Datetime(
        'Last Borrowed On',
        default=lambda self: fields.Datetime.now())

    # Other fields
    # Boolean(string)的值为True 或False，可传入一个字符串文本 位置参数
    active = fields.Boolean('Active?', default=True)
    # Binary(string)存储文件类二进制文件，只需一个字符串文本 位置参数。它可由Python使用 base64编码字符串进行处理
    image = fields.Binary('Cover')

    # Relational Fields
    publisher_id = fields.Many2one(
        'res.partner', string='Publisher')
    # 在数据库层面上，many-to-many关联不会在已有表中添加任何列
    # 而是自动创建一个关联表来存储记录间的关联，该表仅有两个 ID 字段，为两张关联表的外键。默认关联表名由两个表名中间加下划线并在最后加上_rel 来组成
    author_ids = fields.Many2many(
        'res.partner',
        'library_book_res_partner_rel',  # 要使用的关联表名
        string='Authors',
    )
    # store=False 默认不在数据库中存储
    publisher_country_id = fields.Many2one(
        'res.country', string='Publisher Country',
        compute='_compute_publisher_country',
        store=False,
        inverse='_inverse_publisher_country',
        search='_search_publisher_country',
    )
    # SQL约束加在数据表定义中，并由PostgreSQL直接执行。它由_sql_constraints类属性来定义。这是一个元组组成的列表，并且每个元组的格式为(name, code, error)
    # name是约束标识名
    # code是约束的PostgreSQL语法
    # error是在约束验证未通过时向用户显示的错误消息
    # 向图书模型添加两个SQL约束。一条是唯一性约束，用于通过标题和出版日期是否相同来确保没有重复的图书；另一条是检查出版日期是否为未出版
    _sql_constraints = [
        ('library_book_name_date_uq',  # 约束唯一标识符
         'UNIQUE (name, date_published)',  # 约束 SQL 语法
         'Book title and publication date must be unique'),  # 消息
        ('library_book_check_date',
         'CHECK (date_published <= current_date)',
         'Publication date must not be in the future.'),
    ]

    @api.multi
    def _check_isbn(self):
        self.ensure_one()
        isbn = self.isbn.replace('-', '')  # 为保持兼容性 Alan 自行添加
        digits = [int(x) for x in isbn if x.isdigit()]
        if len(digits) == 13:
            ponderations = [1, 3] * 6
            terms = [a * b for a, b in zip(digits[:12], ponderations)]
            remain = sum(terms) % 10
            check = 10 - remain if remain != 0 else 0
            return digits[-1] == check

    # 对于记录的逻辑，使用 @ api.multi装饰器
    # @api.multi装饰器可以不写，因为这是模型方法的默认值。这里保留以示清晰
    @api.multi
    def button_check_isbn(self):
        for book in self:
            # _logger.info('library_book.py >>> def button_check_isbn >>> self.isbn :%s', self.isbn)
            if not book.isbn:
                raise Warning('Please provide an ISBN for %s' % book.name)
            if book.isbn and not book._check_isbn():
                raise Warning('%s is an invalid ISBN' % book.isbn)
            # 模型方法无需返回值，但此处需至少返回 True 值。因为不是所有XML-RPC客户端实现都支持None/Null空值，这种情况若返回空值则会导致抛出错误
            # if book._check_isbn():
            #     raise Warning('%s is a vaild ISBN' %  book.isbn)
            return True

    # Python 约束可使用自定义代码来检查条件。检查方法应添加@api.constrains装饰器，并且包含要检查的字段列表，
    # 其中任意字段被修改就会触发验证，并且在未满足条件时抛出异常。就图书应用来说，一个明显的示例就是防止插入不正确的 ISBN 号。
    # 我们已经在_check_isbn()方法中书写了 ISBN 的校验逻辑。可以在模型约束中使用它来防止保存错误数据
    @api.constrains('isbn')
    def _constrain_isbn_valid(self):
        for book in self:
            if book.isbn and not book._check_isbn():
                raise ValidationError('%s is an invalid ISBN' % book.isbn)

    # 计算如果依赖其它字段的话就需要使用 @ api.depends装饰器，通常都会依赖其它字段
    @api.depends('publisher_id.country_id')
    def _compute_publisher_country(self):
        for book in self:
            book.publisher_country_id = book.publisher_id.country_id

    def _inverse_publisher_country(self):
        for book in self:
            book.publisher_id.country_id = book.publisher_country_id

    def _search_publisher_country(self, opearator, value):
        return [('publisher_id.country_id', operator, value)]
